/*
 * XMLResponseParser.java
 *
 * Created on May 22, 2007, 10:02 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.agent.util;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.ElementEnd;
import org.infoset.xml.Item;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.Validity;
import org.infoset.xml.XMLException;
import org.infoset.xml.filter.ItemFilter;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.infoset.xml.util.DocumentDestination;
import org.infoset.xml.xerces.XMLSchemaValidationFilter;
import org.restlet.Response;
import org.restlet.data.MediaType;
import org.restlet.representation.Representation;

/**
 *
 * @author alex
 */
public class XMLRepresentationParser
{
   DocumentLoader loader = new SAXDocumentLoader();
   boolean validate;
   Map<URI,URL> schemaMap;
   List<Name> topLevelElements;
   
   /** Creates a new instance of XMLResponseParser */
   public XMLRepresentationParser()
   {
      schemaMap = null;
      validate = false;
      topLevelElements = null;
   }
   
   public boolean isValidating() {
      return validate;
   }
   
   public void setValidation(boolean flag)
   {
      validate = flag;
   }
   
   public void addNamespaceLocation(URI namespace,URL location) {
      if (schemaMap==null) {
         schemaMap = new TreeMap<URI,URL>();
      }
      schemaMap.put(namespace,location);
   }
   
   public void setSchemaMap(Map<URI,URL> map)
   {
      schemaMap = map;
   }
   
   public Map<URI,URL> getSchemaMap() {
      return schemaMap;
   }
   
   public List<Name> getAllowedElements() {
      return topLevelElements;
   }
   
   public void setAllowedElements(List<Name> list) {
      topLevelElements = list;
   }
   
   public void addAllowedElement(Name name) 
   {
      if (topLevelElements==null) {
         topLevelElements = new ArrayList<Name>();
      }
      topLevelElements.add(name);
   }
   
   protected ItemFilter createValidationFilter(ItemDestination end) 
      throws XMLException
   {
      final XMLSchemaValidationFilter validate = new XMLSchemaValidationFilter();
      if (schemaMap!=null) {
         for (URI namespace : schemaMap.keySet()) {
            validate.addNamespaceMap(namespace,schemaMap.get(namespace));
         }
      }
      ItemFilter checkValidity = new ItemFilter() {
         ItemDestination output;
         int level = -1;
         public void send(Item item) 
            throws XMLException
         {
            switch (item.getType()) {
               case DocumentItem:
                  level = -1;
                  break;
               case ElementItem:
                  level++;
                  break;
               case ElementEndItem:
                  level--;
                  ElementEnd end = (ElementEnd)item;
                  if (level<0) {
                     if (end.getValidity()!=Validity.VALID) {
                        StringBuilder builder = new StringBuilder();
                        Iterator errors = validate.getErrors();
                        while (errors.hasNext()) {
                           builder.append("\n");
                           builder.append(errors.next().toString());
                        }
                        throw new XMLException("Element "+end.getName()+" is not valid:"+builder.toString());
                     }
                     if (topLevelElements!=null) {
                        Name name = end.getName();
                        if (!topLevelElements.contains(name)) {
                           throw new XMLException("Unexpected document element "+name);
                        }
                     }
                  }
                  break;
            }
            output.send(item);
         }
         public void attach(ItemDestination output) {
            this.output = output;
         }
      };
      validate.attach(checkValidity);
      checkValidity.attach(end);
      return validate;
   }
   
   public void parse(Response response, ItemDestination dest)
      throws IOException,XMLException
   {
      parse(response.getEntity(),URI.create(response.getRequest().getResourceRef().toString()),dest);
   }
   
   public void parse(Representation rep, ItemDestination dest)
      throws IOException,XMLException
   {
      parse(rep,null,dest);
   }
   
   public void parse(Representation rep, URI baseURI, ItemDestination dest)
      throws IOException,XMLException
   {
      String charset = rep.getMediaType().getParameters().getValues("charset");
      if (charset==null) {
         charset = "UTF-8";
      }
      Reader r = new InputStreamReader(rep.getStream(),charset);
      if (validate) {
         dest = createValidationFilter(dest);
      }
      loader.generate(r,baseURI,dest);
   }
   
   public Document load(Response response)
      throws IOException,XMLException
   {
      DocumentDestination dest = new DocumentDestination();
      parse(response,dest);
      return dest.getDocument();
   }
   
   public Document load(Representation rep)
      throws IOException,XMLException
   {
      DocumentDestination dest = new DocumentDestination();
      parse(rep,null,dest);
      return dest.getDocument();
   }
   
   public static boolean isXML(MediaType type)
   {
      String name = type!=null ? type.getName() : null;
      return MediaType.TEXT_XML.getName().equals(name) || MediaType.APPLICATION_XML.getName().equals(name);
   }
   
   public static boolean isXMLTyped(MediaType type)
   {
      String name = type!=null ? type.getName() : null;
      return name!=null && name.endsWith("+xml");
   }
}
